﻿using System.Collections;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Devil
{
    public static partial class ParallelUtils
    {
        #region schedule commands API
        public static ParallelDispatcher.CombinedHandler NewCombination(string info = null)
        {
            return new ParallelDispatcher.CombinedHandler(info);
        }

        public static ParallelDispatcher.Handler GetParallelHandler(this object target)
        {
            //if (target is ParallelDispatcher.Handler)
            //    return (ParallelDispatcher.Handler)target;
            //else 
            if (target is ICommandHandler)
                return ((ICommandHandler)target).GetHandler();
            else
                return default;
        }

        public static ParallelDispatcher.Handler Combine(ParallelDispatcher.Handler cmd1, ParallelDispatcher.Handler cmd2)
        {
            if (!cmd2.IsAlive)
                return cmd1;
            if (!cmd1.IsAlive)
                return cmd2;
#if UNITY_EDITOR
            var combine = new ParallelDispatcher.CombinedHandler($"comb<{cmd1}, {cmd2}>");
#else
            var combine = new ParallelDispatcher.CombinedHandler("default");
#endif
            combine.Combine(cmd1);
            combine.Combine(cmd2);
            return combine.GetHandler();
        }

        public static void Combine(ref this ParallelDispatcher.CombinedHandler combine, object obj)
        {
            if (obj is ICommandHandler)
                combine.Combine(((ICommandHandler)obj).GetHandler());
        }

        public static ParallelDispatcher.Handler Schedule(this ICommand cmd, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            return ParallelDispatcher.GetInstance().Schedule(cmd, mainThread, dependent);
        }

        public static ParallelDispatcher.Handler Schedule(this System.Action action, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            if (action == null)
                throw new System.ArgumentNullException("action");
            return ParallelDispatcher.GetInstance().Schedule(new Cmd0(action), mainThread, dependent);
        }

        public static ParallelDispatcher.Handler Schedule<T>(this System.Action<T> action, T arg, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            if (action == null)
                throw new System.ArgumentNullException("action");
            return ParallelDispatcher.GetInstance().Schedule(new Cmd1<T>(action, arg), mainThread, dependent);
        }

        public static ParallelDispatcher.Handler Schedule(this IYieldCommand cmd, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            return ParallelDispatcher.GetInstance().Schedule(cmd, mainThread, dependent);
        }

        public static ParallelDispatcher.Handler Schedule(this YieldAction action, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            if (action == null)
                throw new System.ArgumentNullException("action");
            return ParallelDispatcher.GetInstance().Schedule(new CmdYield(action), mainThread, dependent);
        }

        public static ParallelDispatcher.Handler Schedule<T>(this YieldAction<T> action, T arg, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            if (action == null)
                throw new System.ArgumentNullException("action");
            return ParallelDispatcher.GetInstance().Schedule(new CmdYield1<T>(action, arg), mainThread, dependent);
        }

        public static ParallelDispatcher.Handler Schedule<T>(this ICommandParallelFor<T> parallel, IEnumerable iter, ParallelDispatcher.Handler dependent = default)
        {
            return ParallelDispatcher.GetInstance().Schedule(parallel, iter, dependent);
        }

        public static ParallelDispatcher.Handler Schedule(this ICommandParallelFor<int> parallel, int count, ParallelDispatcher.Handler dependent = default)
        {
            return ParallelDispatcher.GetInstance().Schedule(parallel, count, dependent);
        }

        public static ParallelDispatcher.Handler ScheduleParallel<T>(this System.Action<T> action, IEnumerable iter, ParallelDispatcher.Handler dependent = default)
        {
            if (action == null)
                throw new System.ArgumentNullException("action");
            return ParallelDispatcher.GetInstance().Schedule(new CmdParallel<T>(action), iter, dependent);
        }

        public static ParallelDispatcher.Handler ScheduleParallel(this System.Action<int> action, int count, ParallelDispatcher.Handler dependent = default)
        {
            if (action == null)
                throw new System.ArgumentNullException("action");
            return ParallelDispatcher.GetInstance().Schedule(new CmdParallel<int>(action), count, dependent);
        }

        public static ParallelDispatcher.Handler Schedule(this ILoopCommand cmd, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            return ParallelDispatcher.GetInstance().Schedule(cmd, mainThread, dependent);
        }

        public static ParallelDispatcher.Handler ScheduleLooper(this System.Action<float> action, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            if (action == null)
                throw new System.ArgumentNullException("action");
            return ParallelDispatcher.GetInstance().Schedule(new CmdLooper(action), mainThread, dependent);
        }

        public static ParallelDispatcher.Handler Wait(this IYieldInstruction instruction, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            if (instruction == null)
                throw new System.ArgumentNullException("instruction");
            return ParallelDispatcher.GetInstance().Schedule(new CmdInstruction(instruction), mainThread, dependent);
        }

        public static ParallelDispatcher.Handler Wait(this ValueDelegate<bool> waitOnCondition, bool mainThread, ParallelDispatcher.Handler dependent = default)
        {
            if (waitOnCondition == null)
                throw new System.ArgumentNullException("waitOnCondition");
            return ParallelDispatcher.GetInstance().Schedule(new CmdInstruction(waitOnCondition), mainThread, dependent);
        }

        #endregion

        #region parallel command buffer

        public static void JoinCommand(this ParallelCommandBuffer buffer, ParallelDispatcher.Handler handler)
        {
            buffer.AddCommand(handler, false);
        }

        public static void AppendCommand(this ParallelCommandBuffer buffer, ParallelDispatcher.Handler handler)
        {
            buffer.AddCommand(handler, true);
        }

        public static void JoinCommand(this ParallelCommandBuffer buffer, ICommand cmd, bool mainThread)
        {
            buffer.AddCommand(cmd, mainThread, false);
        }

        public static void AppendCommand(this ParallelCommandBuffer buffer, ICommand cmd, bool mainThread)
        {
            buffer.AddCommand(cmd, mainThread, true);
        }

        public static void JoinCommand(this ParallelCommandBuffer buffer, IYieldCommand cmd, bool mainThread)
        {
            buffer.AddCommand(cmd, mainThread, false);
        }

        public static void AppendCommand(this ParallelCommandBuffer buffer, IYieldCommand cmd, bool mainThread)
        {
            buffer.AddCommand(cmd, mainThread, true);
        }

        public static void JoinCommand<T>(this ParallelCommandBuffer buffer, ICommandParallelFor<T> cmd, IEnumerable iter)
        {
            buffer.AddCommand<T>(cmd, iter, false);
        }

        public static void AppendCommand<T>(this ParallelCommandBuffer buffer, ICommandParallelFor<T> cmd, IEnumerable iter)
        {
            buffer.AddCommand(cmd, iter, true);
        }

        public static void JoinParallelCommand(this ParallelCommandBuffer buffer, ICommandParallelFor<int> cmd, int count)
        {
            buffer.AddCommand(cmd, count, false);
        }

        public static void AppendParallelCommand(this ParallelCommandBuffer buffer, ICommandParallelFor<int> cmd, int count)
        {
            buffer.AddCommand(cmd, count, true);
        }

        public static void JoinCommandScheduler(this ParallelCommandBuffer buffer, ICommandScheduler scheduler)
        {
            buffer.AddCommandScheduler(scheduler, false);
        }

        public static void AppendCommandScheduler(this ParallelCommandBuffer buffer, ICommandScheduler scheduler)
        {
            buffer.AddCommandScheduler(scheduler, true);
        }

        public static void JoinCommand(this ParallelCommandBuffer buffer, System.Action action, bool mainThread)
        {
            buffer.AddCommand(new Cmd0(action), mainThread, false);
        }

        public static void AppendCommand(this ParallelCommandBuffer buffer, System.Action action, bool mainThread)
        {
            buffer.AddCommand(new Cmd0(action), mainThread, true);
        }

        public static void JoinCommand<T>(this ParallelCommandBuffer buffer, System.Action<T> action, T arg, bool mainThread)
        {
            buffer.AddCommand(new Cmd1<T>(action, arg), mainThread, false);
        }

        public static void AppendCommand<T>(this ParallelCommandBuffer buffer, System.Action<T> action, T arg, bool mainThread)
        {
            buffer.AddCommand(new Cmd1<T>(action, arg), mainThread, true);
        }

        public static void JoinCommand(this ParallelCommandBuffer buffer, YieldAction action, bool mainThread)
        {
            buffer.AddCommand(new CmdYield(action), mainThread, false);
        }

        public static void AppendCommand(this ParallelCommandBuffer buffer, YieldAction action, bool mainThread)
        {
            buffer.AddCommand(new CmdYield(action), mainThread, true);
        }

        public static void JoinCommand<T>(this ParallelCommandBuffer buffer, YieldAction<T> action, T arg, bool mainThread)
        {
            buffer.AddCommand(new CmdYield1<T>(action, arg), mainThread, false);
        }

        public static void AppendCommand<T>(this ParallelCommandBuffer buffer, YieldAction<T> action, T arg, bool mainThread)
        {
            buffer.AddCommand(new CmdYield1<T>(action, arg), mainThread, true);
        }

        public static void JoinParallelCommand<T>(this ParallelCommandBuffer buffer, System.Action<T> action, IEnumerable iter)
        {
            buffer.AddCommand(new CmdParallel<T>(action), iter, false);
        }

        public static void AppendParallelCommand<T>(this ParallelCommandBuffer buffer, System.Action<T> action, IEnumerable iter)
        {
            buffer.AddCommand(new CmdParallel<T>(action), iter, true);
        }

        public static void JoinParallelCommand(this ParallelCommandBuffer buffer, System.Action<int> action, int count)
        {
            buffer.AddCommand(new CmdParallel<int>(action), count, false);
        }

        public static void AppendParallelCommand(this ParallelCommandBuffer buffer, System.Action<int> action, int count)
        {
            buffer.AddCommand(new CmdParallel<int>(action), count, true);
        }


        #endregion

#if UNITY_EDITOR
        public static void DisplayProgressBar(this ParallelDispatcher.Handler handler, string title, string content, bool cancelable)
        {
            var cmd = new ProgressBarPlayer();
            cmd.handler = handler;
            cmd.title = title;
            cmd.content = content;
            cmd.cancelable = cancelable;
            cmd.Schedule(true);
        }

        class ProgressBarPlayer : IYieldCommand
        {
            internal string title;
            internal string content;
            internal bool cancelable;
            internal ParallelDispatcher.Handler handler;
            internal ProgressBarPlayer()
            {
            }
            IEnumerator IYieldCommand.Execute()
            {
                if (string.IsNullOrEmpty(title))
                {
                    var cmd = handler.GetCommand();
                    if (cmd == null)
                        title = "Command Handler";
                    else
                        title = cmd.GetType().Name;
                }
                if (string.IsNullOrEmpty(content))
                    content = handler.ToString();
                while (handler.IsAlive)
                {
                    if (cancelable)
                    {
                        var cancel = EditorUtility.DisplayCancelableProgressBar(title, content, handler.GetExecution().progress);
                        if (cancel)
                        {
                            handler.Abort(true);
                            EditorUtility.ClearProgressBar();
                            yield break;
                        }
                    }
                    else
                    {
                        EditorUtility.DisplayCancelableProgressBar(title, content, handler.GetExecution().progress);
                    }
                    yield return null;
                }
                EditorUtility.ClearProgressBar();
            }
        }
#endif
    }

}
